﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using DrawTools.Editor;
using DrawTools.Editor.CanvasFigures;

namespace DrawTools.ViewModels
{
  public class PropertiesFormViewModel
  {
    public static Color NullColor = Color.FromArgb(174, 174, 174);

    private readonly List<CanvasObject> _canvasObjects;

    //TODO refactor this using decorator may be?
    private readonly List<KeyValuePair<CanvasObject, Color>> _borderColors ;
    private readonly List<KeyValuePair<CanvasRectangleBase, Color>> _backColors;
    private readonly List<KeyValuePair<CanvasText, Color>> _textColors;

    //General Properties
    public string Name { get; private set; }

    public Color BorderColor
    {
      get { return GetColorProperty<CanvasObject>(p => p.BorderColor); }
      set { SetColorProperty(p => p.BorderColor, _borderColors, value); }
    }
    
    public int? BorderWidth
    {
      get { return GetCommonProp<CanvasObject, int>(o => o.BorderWidth); } 
      set { SetProperty<CanvasObject, int>(p => p.BorderWidth, value);}
    }

    public LineType? LineType
    {
      get { return GetCommonProp<CanvasObject, LineType>(o => o.LineType); }
      set { SetProperty<CanvasObject, LineType>(p => p.LineType, value); }
    }

    //Arrow
    public bool IsArrowPartVisible
    {
      get { return AreAllOfType<CanvasArrow>(); }
    }

    public bool? IsArrowAtStart
    {
      get { return GetCommonProp<CanvasArrow, bool>(o => o.IsArrowAtStart); }
      set { SetProperty<CanvasArrow, bool>(p => p.IsArrowAtStart, value); }
    }

    public bool? IsArrowAtEnd
    {
      get { return GetCommonProp<CanvasArrow, bool>(o => o.IsArrowAtEnd); }
      set { SetProperty<CanvasArrow, bool>(p => p.IsArrowAtEnd, value); }
    }

    //Back color
    public bool IsBackColorPartVisible
    {
      get { return AreAllOfType<CanvasRectangleBase>(); }
    }

    public Color BackColor
    {
      get { return GetColorProperty<CanvasRectangleBase>(p => p.BackColor); }
      set { SetColorProperty(p => p.BackColor, _backColors, value); }
    }

    public int BackgroundOpacity
    {
      get
      {
        var rect = First<CanvasRectangleBase>();
        
        if (rect == null)
          return 0;

        return rect.BackgroundOpacity * 100 / 255;
      }
      set { SetProperty<CanvasRectangleBase>(p => p.BackgroundOpacity = (byte)(255 * value / 100)); }
    }

    //Text
    public bool IsTextPartVisible
    {
      get { return AreAllOfType<CanvasText>(); }
    }

    public int? FontSize
    {
      get { return GetCommonProp<CanvasText, int>(o => o.FontSize); }
      set { SetProperty<CanvasText, int>(p => p.FontSize, value); }
    }

    public Color TextColor
    {
      get { return GetColorProperty<CanvasText>(p => p.TextColor); }
      set { SetColorProperty(p => p.TextColor, _textColors, value); }
    }
    
    public bool? IsItalic
    {
      get { return GetCommonProp<CanvasText, bool>(o => o.IsItalic); }
      set { SetProperty<CanvasText, bool>(p => p.IsItalic, value); }
    }

    public bool? IsBold
    {
      get { return GetCommonProp<CanvasText, bool>(o => o.IsBold); }
      set { SetProperty<CanvasText, bool>(p => p.IsBold, value); }
    }

    public bool? IsUnderline
    {
      get { return GetCommonProp<CanvasText, bool>(o => o.IsUnderline); }
      set { SetProperty<CanvasText, bool>(p => p.IsUnderline, value); }
    }

    public StringAlignment? TextAlignment
    {
      get { return GetCommonProp<CanvasText, StringAlignment>(o => o.TextAlignment); }
      set { SetProperty<CanvasText, StringAlignment>(p => p.TextAlignment, value); }
    }

    public PropertiesFormViewModel(IEnumerable<CanvasObject> canvasObjects)
    {
      _canvasObjects = canvasObjects.ToList();

      _borderColors = new List<KeyValuePair<CanvasObject, Color>>(_canvasObjects.Count);

      foreach (var canvasObject in _canvasObjects)
      {
        _borderColors.Add(new KeyValuePair<CanvasObject, Color>( canvasObject, canvasObject.BorderColor));
      }

      _backColors = new List<KeyValuePair<CanvasRectangleBase, Color>>(_canvasObjects.Count);

      foreach (var canvasObject in _canvasObjects.OfType<CanvasRectangleBase>())
      {
        _backColors.Add(new KeyValuePair<CanvasRectangleBase, Color>(canvasObject, canvasObject.BackColor));
      }

      _textColors = new List<KeyValuePair<CanvasText, Color>>(_canvasObjects.Count);

      foreach (var canvasObject in _canvasObjects.OfType<CanvasText>())
      {
        _textColors.Add(new KeyValuePair<CanvasText, Color>(canvasObject, canvasObject.TextColor));
      }

      Name = "Canvas Object";

      if (AreAllOfType<CanvasText>())
        Name = "Text";
      else if (AreAllOfType<CanvasArrow>())
        Name = "Arrow/Line";
      else if (AreAllOfType<CanvasEllipse>())
        Name = "Ellipse";
      else if (AreAllOfType<CanvasRectangle>())
        Name = "Rectangle";

      if (_canvasObjects.Count > 1)
        Name += "s";
    }

    private static PropertyInfo GetPropertyInfo<TObject, TProperty>(Expression<Func<TObject, TProperty>> expression)
    {
      Expression body = expression;
      if (body is LambdaExpression)
      {
        body = ((LambdaExpression)body).Body;
      }

      if (body.NodeType == ExpressionType.MemberAccess)
      {
        return ((MemberExpression)body).Member as PropertyInfo;
      }

      throw new ArgumentException("Invaluid expression type");
    }

    private bool AreAllOfType<TObject>() where TObject : CanvasObject
    {
      foreach (CanvasObject o in _canvasObjects)
      {
        if (!(o is TObject))
          return false;
      }
      return true;
    }

    private TObject First<TObject>() where TObject : CanvasObject
    {
      return _canvasObjects.OfType<TObject>().FirstOrDefault();
    }

    private TValue? GetCommonProp<TObject, TValue>(Expression<Func<TObject, TValue>> expression)
      where TValue : struct
      where TObject : CanvasObject
    {
      List<TValue> values = _canvasObjects.OfType<TObject>().Select(o => GetPropertyValue(expression, o)).Distinct().ToList();
      return values.Count == 1 ? values[0] : (TValue?)null;
    }


    private TValue GetPropertyValue<TObject, TValue>(Expression<Func<TObject, TValue>> expression, TObject obj) where TObject : CanvasObject
    {
      PropertyInfo propertyInfo = GetPropertyInfo(expression);
      return (TValue)propertyInfo.GetValue(obj);
    }

    private void SetColorProperty<T>(Expression<Func<T, Color>> expression, IEnumerable<KeyValuePair<T, Color>> list, Color value) where T : CanvasObject
    {
      if (value == NullColor)
      {
        PropertyInfo propertyInfo = GetPropertyInfo(expression);

        foreach (var pair in list)
        {
          propertyInfo.SetValue(pair.Key, pair.Value);
        }
      }
      else
      {
        SetProperty(expression, value);
      }
    }

    private Color GetColorProperty<T>(Expression<Func<T, Color>> expression) where T : CanvasObject
    {
      var color = GetCommonProp(expression);
      return color.HasValue ? color.Value : NullColor;
    }


    private void SetProperty<TObject, TValue>(Expression<Func<TObject, TValue>> expression, TValue? value) where TObject : CanvasObject where TValue:struct 
    {
      if (!value.HasValue)
        return;

      PropertyInfo propertyInfo = GetPropertyInfo(expression);

      foreach (TObject canvasObject in _canvasObjects.OfType<TObject>())
      {
        propertyInfo.SetValue(canvasObject, value.Value, null);
      }
    }

    private void SetProperty<TObject>(Action<TObject> setter) where TObject : CanvasObject
    {
      foreach (TObject canvasObject in _canvasObjects.OfType<TObject>())
      {
        setter(canvasObject);
      }
    }
  }
}